﻿/*
   Open Intel 
   Copyright © 2011 – ISC. All Rights Reserved.
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Syndication;
using System.ServiceModel.Web;
using System.Threading;
using System.Xml;
using ISC.MapDotNetServer.Common;
using ISC.MapDotNetServer.Common.Data;
using ISC.MapDotNetServer.Common.Maps;
using ISC.MapDotNetServer.Core.Services;
using ISC.RIM.WPF.Projections;
using ISC.RIM.WPF;
using Map = ISC.MapDotNetServer.Common.Maps.Map;

namespace OI.Web.RSS
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class RSSFeed
    {
        [Description("Open Intel RSS Feed")]
        [WebGet(UriTemplate = "?MapID={mapID}&LayerID={layerID}")]
        public Rss20FeedFormatter GetFeed(string mapID, string layerID)
        {
            // create a new feed
            var synItems = new List<SyndicationItem>();
            var feed = new SyndicationFeed(layerID, null, null);
            feed.AddGeoRSSNamespace();

            try
            {
                // setup UX service for use and prepare for async call
                var service = ResourceManager.FeatureServiceSingle();
                var wait = new EventWaitHandle(false, EventResetMode.ManualReset);
                var asyncState = new Tuple<EventWaitHandle, string[]>(wait, new string[1]);

                // get layer from map
                var layer = GetBaselineMap(mapID).GetLayerByID(layerID);
                var rssMd = RSSMetaData.FactoryFromLayer(layer);
                if (rssMd != null)
                {
                    // query request
                    var qr = new QueryRequest
                    {
                        ReturnShapes = true,
                        ReturnShapeBounds = false,
                        MaxRows = rssMd.ItemCount,
                        ReturnResultSetBounds = false,
                        ShapeFilterOperation = ShapeFilterOperations.NO_OPERATION,
                        ReturnTypes = false
                    };

                    // query
                    var query = new Query
                    {
                        DataSourceSpecification =
                            CreateDataSourceSpecication(mapID, layerID, ""),
                        DataSelectionKeyword = Query.DataSelectionKeywords.All,
                        Sort =
                        {
                            Field = rssMd.DateTimeColumn,
                            Direction = QuerySort.SortDirections.DESC
                        }
                    };

                    // add columns
                    List<string> qStrItems = RSSMetaData.GetQueryColumns(rssMd);
                    foreach (var qStrItem in qStrItems)
                    {
                        query.Fields.Add(qStrItem);
                    }
                    qr.Queries.Add(query);

                    // call feature service
                    service.BeginQuery(qr.Serialize(false), Guid.NewGuid(), GetFeedCompleted, asyncState);
                    wait.WaitOne();

                    // get results
                    string results = asyncState.Item2[0];
                    var queryResponse = QueryResponse.Deserialize(results);

                    // look for error
                    // check error at query-group level
                    if (string.IsNullOrEmpty(queryResponse.Error))
                    {
                        var qrt = queryResponse.QueryResultTables[0];
                        if (string.IsNullOrEmpty(qrt.Error))
                        {
                            foreach (var row in qrt.QueryResultRows)
                            {
                                int idx = 0;
                                var vals = row.Values;

                                // parse values out
                                idx = RSSMetaData.FindQueryStringIndexByName(rssMd.IDColumn, qStrItems);
                                string id = idx != -1 ? vals[idx] : null;
                                idx = RSSMetaData.FindQueryStringIndexByName(rssMd.DateTimeColumn, qStrItems);
                                DateTime time = idx != -1 ? Convert.ToDateTime(vals[idx]) : DateTime.Now;
                                idx = RSSMetaData.FindQueryStringIndexByName(rssMd.TitleColumn, qStrItems);
                                string title = idx != -1 ? vals[idx] : null;
                                idx = RSSMetaData.FindQueryStringIndexByName(rssMd.ContentColumn, qStrItems);
                                string content = idx != -1 ? vals[idx] : null;
                                idx = RSSMetaData.FindQueryStringIndexByName(rssMd.UriColumn, qStrItems);
                                Uri uri = idx != -1 ? new Uri(vals[idx]) : null;

                                // item
                                var item = new SyndicationItem(title, content, uri, id, time);

                                // converts any shape to a point (centroid)
                                // all data is in spherical mercator
                                var pt = row.Shape.Bounds.Center.ToSysWinPoint();
                                var latlon = SphericalMercatorProj.ConvertMapUnitToLatLon(pt);
                                item.AddGeoRSSPoint(latlon.Y, latlon.X);

                                // add
                                synItems.Add(item);
                            }
                        }
                    }
                }
            }
            // swallow
            catch{}

            // add items to feed and return feed
            feed.Items = synItems;
            return new Rss20FeedFormatter(feed);
        }


        static void GetFeedCompleted(IAsyncResult result)
        {
            var service = ResourceManager.FeatureServiceSingle();
            var resultUnpacked = (Tuple<EventWaitHandle, string[]>)result.AsyncState;
            var resultStr = service.EndQuery(result);

            resultUnpacked.Item2[0] = resultStr;
            resultUnpacked.Item1.Set();
        }


        private static AbstractDataSourceSpecification CreateDataSourceSpecication(string mapID, string layerID, string whereClause)
        {
            return new MapLayerDataSourceSpecification
            {
                MapID = mapID,
                LayerID = layerID,
                WhereClauseOverride = whereClause,
            };
        }


        public static Map GetBaselineMap(string mapID)
        {
            return ResourceManager.MapServiceSingle().MapDescriber.GetReferenceMap(mapID);
        }


        public static SphericalMercatorTileProjection SphericalMercatorProj
        {
            get
            {
                return _SphericalMercatorProj ?? (_SphericalMercatorProj = new SphericalMercatorTileProjection());
            }
        }
        private static SphericalMercatorTileProjection _SphericalMercatorProj;
    }



    // helper for GeoRSS
    public static class SyndicationFeedExtension
    {
        private static readonly XmlQualifiedName GeoRssXmlQualifiedName = new XmlQualifiedName("georss", "http://www.w3.org/2000/xmlns/");
        private const string GeoRssNamespace = "http://www.georss.org/georss";

        public static void AddGeoRSSPoint(this SyndicationItem feedItem, double latitude, double longitude)
        {
            feedItem.ElementExtensions.Add(new SyndicationElementExtension("point", GeoRssNamespace, latitude + " " + longitude));
        }

        public static void AddGeoRSSNamespace(this SyndicationFeed feed)
        {
            feed.AttributeExtensions.Add(GeoRssXmlQualifiedName, GeoRssNamespace);
        }
    }


    public class RSSMetaData
    {
        public string MetaDataString { get; private set; }
        public string IDColumn { get; private set; }
        public string DateTimeColumn { get; private set; }
        public string TitleColumn { get; private set; }
        public string ContentColumn { get; private set; }
        public string UriColumn { get; private set; }
        public int ItemCount { get; private set; }

        // create an instance of this from layer metadata
        public static RSSMetaData FactoryFromLayer(Layer layer)
        {
            if (layer != null &&
                layer.MetaData != null && 
                layer.MetaData.ContainsKey(Configuration.RSSEnableMetaKey))
            {
                string rssStr = layer.MetaData[Configuration.RSSEnableMetaKey];
                if (!string.IsNullOrEmpty(rssStr))
                {
                    var items = rssStr.Split(',');
                    if (items.Length == 6)
                    {
                        var rssMd = new RSSMetaData
                        {
                            MetaDataString = rssStr,
                            IDColumn = items[0],
                            DateTimeColumn = items[1],
                            TitleColumn = items[2],
                            ContentColumn = items[3],
                            UriColumn = items[4],
                            ItemCount = Convert.ToInt32(items[5])
                        };
                        return rssMd;
                    }
                }
            }
            return null;
        }


        // prevent default constructor, use factory
        private RSSMetaData() { }


        // creates a column list
        public static List<string> GetQueryColumns(RSSMetaData mdItem)
        {
            return !string.IsNullOrEmpty(mdItem.MetaDataString) ? 
                mdItem.MetaDataString.Split(',').TakeWhile(i=>!string.IsNullOrEmpty(i)).ToList() : 
                null;
        }


        // returns the index of the column in the supplied query string
        public static int FindQueryStringIndexByName(string name, List<string> queryCols)
        {
            if (queryCols == null || queryCols.Count == 0)
            {
                return -1;
            }

            int idx = 0;
            foreach (var item in queryCols)
            {
                if (item.Equals(name, StringComparison.CurrentCultureIgnoreCase))
                {
                    return idx;
                }
                idx++;
            }
            return -1;
        }

    }
}